using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using Meebey.SmartIrc4net;

namespace Thunder.Irc.Parser
{
    public delegate void ChannelHandler(string channel);

    public delegate void WriteTextHandler(string channel, IrcMessageData data, string message);

    public delegate void MemberHandler(string channel, string name);

    /// <summary>
    /// Manager for parsing <see cref="IrcMessageData"/> messages
    /// </summary>
    public sealed class ParserManager
    {
        private readonly Dictionary<ReceiveType, List<IMessageParser>> customParsers = new Dictionary<ReceiveType, List<IMessageParser>>();
        private readonly Dictionary<ReceiveType, List<IMessageParser>> defaultParsers = new Dictionary<ReceiveType, List<IMessageParser>>();

        /// <summary>
        /// Gets the current <see cref="IrcClient"/> instance
        /// </summary>
        public IrcClient IrcClient { get; private set; }

        /// <summary>
        /// Gets or sets if the join, part and quit messages should be shown
        /// </summary>
        public bool ShowJoinPart { get; set; }

        public event ChannelHandler ChannelAdded;
        public event ChannelHandler ChannelClosed;
        public event ChannelHandler ChannelOpened;

        public event MemberHandler MemberAdded;
        public event MemberHandler MemberRemoved;
        public event WriteTextHandler TextAdded;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="client">The current <see cref="IrcClient"/> instance</param>
        public ParserManager(IrcClient client)
        {
            this.IrcClient = client;
        }

        /// <summary>
        /// Invoke this method in order to add a channel
        /// </summary>
        /// <param name="channel">Name of the channel to be added</param>
        public void AddChannel(string channel)
        {
            this.OnChannelAdded(channel);
        }

        /// <summary>
        /// Invoke this method in order to add a member to a channel
        /// </summary>
        /// <param name="channel">Name of the channel to add the member to</param>
        /// <param name="member">Name of the member to add</param>
        public void AddMember(string channel, string member)
        {
            this.OnMemberAdded(channel, member);
        }

        /// <summary>
        /// Invoked this method in order to close a opened channel
        /// </summary>
        /// <param name="channel">Name of the channel to close</param>
        public void CloseChannel(string channel)
        {
            this.OnChannelClosed(channel);
        }

        /// <summary>
        /// Initializes the <see cref="ParserManager"/>, and loads all available parsers
        /// </summary>
        public void Initialize()
        {
            this.LoadCustomParsers();
            this.LoadDefaultParsers();
        }

        /// <summary>
        /// Creates the permitted permissions for the custom parsers
        /// </summary>
        /// <returns></returns>
        private static PermissionSet CreatePermissionInstance()
        {
            PermissionSet permissionSet = new PermissionSet(PermissionState.None);

            return permissionSet;
        }

        /// <summary>
        /// Invoke this method in order to open a channel in edit mode
        /// </summary>
        /// <param name="channel">Name of the channel to open</param>
        public void OpenChannel(string channel)
        {
            this.OnChannelOpened(channel);
        }

        /// <summary>
        /// Parses the <see cref="IrcMessageData"/> using either default or custom message parsers
        /// </summary>
        /// <param name="data">The <see cref="IrcMessageData"/> to be parsed</param>
        public void ParseMessage(IrcMessageData data)
        {
            CancelMessageProcessingEventArgs args = new CancelMessageProcessingEventArgs(data);
            if (this.customParsers.ContainsKey(data.Type) == true)
            {
                foreach (IMessageParser customParser in this.customParsers[data.Type])
                {
                    PermissionSet permissionSet = CreatePermissionInstance();
                    permissionSet.PermitOnly();

                    customParser.Parse(args);
                    if (args.Cancel == true)
                    {
                        break;
                    }
                }
            }

            if (args.Cancel == true)
            {
                return;
            }

            if (this.defaultParsers.ContainsKey(data.Type) == true)
            {
                foreach (IMessageParser defaultParser in this.defaultParsers[data.Type])
                {
                    defaultParser.Parse(args);
                }
            }
            else
            {
                this.WriteStatus(string.Format(Localized.ParserTypeNotFound, data.Type, data.Message), ReceiveType.Error);
            }
        }

        /// <summary>
        /// Invoke this method in order to remove a member from a channel
        /// </summary>
        /// <param name="channel">Name of the channel to remove the member from</param>
        /// <param name="member">Name of the member to remove</param>
        public void RemoveMember(string channel, string member)
        {
            this.OnMemberRemoved(channel, member);
        }

        /// <summary>
        /// Writes a string to the status page
        /// </summary>
        /// <param name="message">The message to write</param>
        /// <param name="receiveType">The <see cref="ReceiveType"/> to use for the message</param>
        public void WriteStatus(string message, ReceiveType receiveType)
        {
            this.WriteText(string.Empty, new IrcMessageData(null, "#System", "#System", string.Empty, string.Empty, string.Empty, message, message, receiveType, ReplyCode.Null), message);
        }

        /// <summary>
        /// Adds a message to a channel
        /// </summary>
        /// <param name="channel">The name of the channel to add the message to</param>
        /// <param name="data">The originating <see cref="IrcMessageData"/> for the message</param>
        /// <param name="message">The message text to add</param>
        /// <remarks>Set the channel to <see cref="string.Empty"/> in order to add the message to the status page</remarks>
        public void WriteText(string channel, IrcMessageData data, string message)
        {
            this.OnTextAdded(channel, data, message);
        }

        /// <summary>
        /// Loads the custom <see cref="IMessageParser"/>'s for the parser directory
        /// </summary>
        private void LoadCustomParsers()
        {
            if (Directory.Exists("Parsers") == false)
            {
                return;
            }

            string parsersPath = Environment.CurrentDirectory + "\\Parsers";
            string[] files = Directory.GetFiles(parsersPath, "*.dll");

            foreach (string file in files)
            {
                Assembly assembly;
                try
                {
                    assembly = Assembly.LoadFile(file);
                }
                catch (BadImageFormatException)
                {
                    continue;
                }

                PermissionSet permissionSet = CreatePermissionInstance();
                permissionSet.PermitOnly();
                this.LoadParsers(assembly, this.customParsers);
            }
        }

        /// <summary>
        /// Loads the list of default <see cref="IMessageParser"/>'s
        /// </summary>
        private void LoadDefaultParsers()
        {
            this.LoadParsers(Assembly.GetAssembly(this.GetType()), this.defaultParsers);
        }

        /// <summary>
        /// Loads all available parsers from the provided assembly into the provided parser dictionary
        /// </summary>
        /// <param name="assembly">The <see cref="Assembly"/> to load the parsers from</param>
        /// <param name="parsers">The list of parsers to load the parser into</param>
        private void LoadParsers(Assembly assembly, IDictionary<ReceiveType, List<IMessageParser>> parsers)
        {
            Type[] types = assembly.GetTypes();

            foreach (Type type in types)
            {
                if (type.IsSubclassOf(typeof (MessageParser)) == false)
                {
                    continue;
                }

                MessageParser parser = Activator.CreateInstance(type) as MessageParser;
                if (parser == null)
                {
                    continue;
                }

                parser.Initialize(this);
                if (parsers.ContainsKey(parser.MessageType) == false)
                {
                    parsers.Add(parser.MessageType, new List<IMessageParser>());
                }

                parsers[parser.MessageType].Add(parser);
            }
        }

        private void OnChannelAdded(string channel)
        {
            if (this.ChannelAdded != null)
            {
                this.ChannelAdded(channel);
            }
        }

        private void OnChannelClosed(string channel)
        {
            if (this.ChannelClosed != null)
            {
                this.ChannelClosed(channel);
            }
        }

        private void OnChannelOpened(string channel)
        {
            if (this.ChannelOpened != null)
            {
                this.ChannelOpened(channel);
            }
        }

        private void OnMemberAdded(string channel, string member)
        {
            if (this.MemberAdded != null)
            {
                this.MemberAdded(channel, member);
            }
        }

        private void OnMemberRemoved(string channel, string member)
        {
            if (this.MemberRemoved != null)
            {
                this.MemberRemoved(channel, member);
            }
        }

        private void OnTextAdded(string channel, IrcMessageData data, string message)
        {
            if (this.TextAdded != null)
            {
                this.TextAdded(channel, data, message);
            }
        }
    }
}